package com.zhichat.infrastructure.exception;

import com.zhichat.infrastructure.exception.http.CommonException;
import com.zhichat.infrastructure.util.Utils;
import com.zhichat.web.api.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 全局统一异常处理
 *
 * @ResponseStatus(HttpStatus.BAD_REQUEST) 注解，是返回给前端页面的code值，和在body体内的code不冲突，可以不一样，有个例↓↓
 * 如果返回的是ResponseEntity实体，可以只在在参数中指定HttpStatus，上面注解会被参数HttpStatus值覆盖
 * 如果返回的是R对象，R属于body体对象，前端页面的code值，则是上面注解指定的，默认值是500
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(NumberFormatException.class)
    public ResponseEntity<R> handleException(HttpServletRequest request, NumberFormatException e) {
        return fail(e, request);
    }

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<R> handleException(HttpServletRequest request, ConstraintViolationException e) {
        return fail(e, request);
    }

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<R> handleException(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        saveLog(Utils.requestInfo(request), e.toString());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, String> errMap = e.getBindingResult().getFieldErrors()
//                .stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
//                .stream().collect(HashMap::new, (x, y) -> x.put(y.getField(), y.getDefaultMessage()), HashMap::putAll);
                .stream().collect(Collectors.toMap(FieldError::getField, x -> x.getDefaultMessage(), (v1, v2) -> v1.concat(",".concat(v2))));
        return new ResponseEntity<>(R.fail(400, errMap.toString(), Utils.requestInfo(request)), headers, HttpStatus.BAD_REQUEST);
    }

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(BindException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R handleBindException(HttpServletRequest request, BindException e) {
        String requestInfo = Utils.requestInfo(request);
        saveLog(requestInfo, e);
        String message = e.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";"));
        return R.fail(100020, message, requestInfo);
    }

    @ResponseStatus
    @SuppressWarnings("rawtypes")
    @ExceptionHandler(DuplicateKeyException.class)
    public R handleException(HttpServletRequest request, DuplicateKeyException e) {
        log.error(e.getMessage(), e);
        return R.fail(100021, e.getMessage(), Utils.requestInfo(request));
    }

    @ResponseStatus
    @SuppressWarnings("rawtypes")
    @ExceptionHandler(CommonException.class)
    public R handleException(HttpServletRequest request, CommonException e) {
        log.error(e.getMessage(), e);
        return R.fail(e.getCode(), Objects.nonNull(e.getData()) ? e.getData().toString() : "服务器异常", Utils.requestInfo(request));
    }

    @ResponseStatus
    @SuppressWarnings("rawtypes")
    @ExceptionHandler(Throwable.class)
    public R handleException(HttpServletRequest request, Throwable t) {
        log.error(t.getMessage(), t);
        return R.fail(9999, "服务器异常", Utils.requestInfo(request));
    }

    private void saveLog(String requestInfo, Exception e) {
        log.error("requestInfo:{}, exception:{}", requestInfo, e);
    }

    private void saveLog(String requestInfo, String e) {
        log.error("requestInfo:{}, exception:{}", requestInfo, e);
    }

    private ResponseEntity<R> fail(Exception e, HttpServletRequest request) {
        log.error(e.getMessage(), e);
        saveLog(Utils.requestInfo(request), e.toString());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<>(R.fail(cn.hutool.http.HttpStatus.HTTP_BAD_REQUEST, e.getMessage(), Utils.requestInfo(request)), headers, HttpStatus.BAD_REQUEST);
    }
}

